#!/usr/bin/env python
# _*_ coding: UTF-8 _*_

import sys, os, termios
from SSHBatch.Log import Log
from SSHBatch import Utilities

try: import readline
except: pass

class Invoker:
    GLOBAL, CLUSTER, HOST, SSH = 1, 2, 3, 4
    OK, FAIL, QUIT = 1, 2, 3
    CS = \
    {
    GLOBAL:"Global",
    CLUSTER:"Cluster",
    HOST:"Host",
    SSH:"Ssh"
    }
    
    def __init__(self, ctx):
        self.config = ctx["config"]
        self.logger = ctx["logger"]
        self.reactor = ctx["reactor"]
        self.sshagents = ctx["sshagents"]
        self.rt = ctx["rt"]
        self.focus = ""
        self.capture = ""
        self.buffers = []
        
        self.tokens = [self.splitCommand(entry[3:]) for entry in Invoker.__dict__ \
                if entry.find("cmd") == 0]
        for cluster in self.config["Clusters"].keys():
            self.tokens.append(["cluster", cluster])
        for host in self.config["Hosts"].keys():
            self.tokens.append(["host", host])
            self.tokens.append(["focus", host])
            self.tokens.append(["ssh", host])        
            self.tokens.append(["connect", host])
            self.tokens.append(["disconnect", host])
            self.tokens.append(["keof", host])
            self.tokens.append(["capture", host])
            self.tokens.append(["hrun", host])
            self.tokens.append(["hsend", host])

    def completer(self, text, state):
        cand = self.getAvailableTokens(readline.get_line_buffer()) + [None]
        return cand[state]

    def dispatch(self, line):
        if self.rt["mode"] == Invoker.SSH:
            if line.find(self.config["Global"]["sshbatchid"]) != 0:
                self.logger.writef("${UP}${BOL}${CLEAR_EOL}")
                self.sshagents[self.rt["hostid"]].last(1)
                self.sshagents[self.rt["hostid"]].write(line)
                return Invoker.OK
            else:
                line = line[len(self.config["Global"]["sshbatchid"]):]
        try:
            tokens = Utilities.cmd_parse(line)
        except SyntaxError, e:
            self.logger.write("%s\n" % str(e))
            return Invoker.FAIL
        
        if len(tokens) >= 1: 
            func = self.lookupCommand("cmd", tokens[0], Invoker.__dict__)
            if func:
                ret = func(self, tokens[1:], line)
            elif func == None: # Command not found
                ret = self.execDefault(tokens, line)
            else: # Command ambigutous
                ret = Invoker.FAIL
        else: # Empty line
            if self.rt["mode"] in [Invoker.HOST, Invoker.CLUSTER]:
                ret = self.cmdLast([], line)
            else:
                ret = Invoker.OK
                
        if self.rt["mode"] == Invoker.SSH and ret != Invoker.QUIT:
            self.sshagents[self.rt["hostid"]].last(2)
        return ret

    def execDefault(self, params, rawline):
        if params[0][0] == "!": # Run command locally
            command = rawline.strip()[1:]
            os.system(command)
            return Invoker.OK
        if self.rt["mode"] == Invoker.GLOBAL:
            self.logger.write("Unrecognized token '%s'!\n" % params[0])
            return Invoker.FAIL
        return self.sshChat(rawline)

    def cmdHelp(self, params, rawline):
        self.logger.write("""Command list:     
    global                Change to global mode [G|C|H|S]
    cluster {cluster}     Change to cluster mode [G|C|H|S]
    host {host}           Change to host mode [G|C|H|S]
    ssh [{host}]          Change to ssh mode [G|C|H|S]
    
    connect [{host} ...]  Open ssh connection [G|C|H|S]
    disconnect [{host} ...] Close ssh connection [G|C|H|S]
    kcontrol {char}       Send key ^-{char} to ssh connection [C|H|S]
    keof [{host} ...]     Send key EOF to ssh connection [G|C|H|S]
    focus [{host}]        Set focus on host or stop focusing [G|C|H]
    
    show buffer [{buf-no}]Display content of buffer
    capture [{host}]      Capture output into buffer or stop capturing [G|C|H]
    edit %{buf-no}|fpath  Edit content of buffer or file
    transmit %{buf-no}|{fpath}  Transmit buffer or local file to ssh connection [C|H|S]
    load fpath            Load local file into buffer
    delete {buf-no}       Discard buffer
    
    run {string}          Send string to ssh connection with NL [C|H|S]
    send {string}         Send string to ssh connection without NL [C|H|S]
    hrun host {string}    Send string to host ssh connection with NL [G|C|H|S]
    hsend host {string}   Send string to host ssh connection without NL [G|C|H|S]
    
    last [{num-lines}]    Show last output lines of ssh connection [C|H|S]
    reply password        Send configured password to ssh connection [C|H|S]
    show running|config   Get sshbatch information [G|C|H|S]
    history [{num-lines}] Show command history [G|C|H|S]
    help                  Print this screen [G|C|H|S]
    quit                  Quit [G|C|H|S]
    !command              Run command locally [G|C|H|S]

    Usable Modes: [G]lobal [C]luster [H]ost [S]sh

See README for more help.
""")
        return Invoker.OK

    def cmdEdit(self, params, rawline):
        if len(params) < 1:
            self.logger.write("Incomplete command(edit): %{buf-no}|fpath\n")
            return Invoker.FAIL
        if params[0][0] == "%":
            try:
                nobuffer = int(params[0][1:])
            except ValueError:
                self.logger.write("Invalid buffer number %s\n" % params[0])
                return Invoker.FAIL
            if len(self.buffers) <= nobuffer:
                self.logger.write("Buffer number out of range\n")
                return Invoker.FAIL
            fpath = "/tmp/sshbatch-%d.tmp" % os.getpid()
            try:
                fh = open(fpath, "wb")
                fh.write(self.buffers[nobuffer])
                fh.close()
                os.system(self.config["Global"]["editor"] + " " + fpath)
                fh = open(fpath, "rb")
                self.buffers[nobuffer] = fh.read(4096*1024)
                fh.close()
                os.unlink(fpath)
            except (Exception, KeyboardInterrupt, SystemExit), e:
                return Invoker.FAIL
        else:
            os.system(self.config["Global"]["editor"] + " " + params[0])
        return Invoker.OK
    
    def cmdTransmit(self, params, rawline):
        if len(params) < 1:
            self.logger.write("Incomplete command(save): %{buf-no}|{fpath}\n")
            return Invoker.FAIL
        if params[0][0] == "%":
            try:
                nobuffer = int(params[0][1:])
            except ValueError:
                self.logger.write("Invalid buffer number %s\n" % params[0][1:])
                return Invoker.FAIL
            if len(self.buffers) <= nobuffer:
                self.logger.write("Buffer number out of range\n")
                return Invoker.FAIL
            buffer = self.buffers[nobuffer]
        else:
            try:
                fh = open(params[0], "rb")
                buffer = fh.read(4096*1024)
                fh.close()
            except:
                self.logger.write("Can't open source file %s\n" % params[0])
                return Invoker.FAIL            
        return self.sshChat(buffer)
    
    def cmdLoad(self, params, rawline):
        if len(params) < 1:
            self.logger.write("Incomplete command(load): {fpath}\n")
            return Invoker.FAIL
        try:
            fh = open(params[0], "r")
            self.buffers.append(fh.read(4096*1024))
            fh.close()
        except:
            self.logger.write("Failed to read file %s\n" % params[0])
            return Invoker.FAIL
        return Invoker.OK
    
    def cmdDelete(self, params, rawline):
        if len(params) < 1:
            self.logger.write("Incomplete command(delete): {buffer-no}\n")
            return Invoker.FAIL
        try:
            nobuffer = int(params[0])
        except ValueError:
            self.logger.write("Invalid buffer number %s\n" % params[0])
            return Invoker.FAIL
        if nobuffer >= len(self.buffers):
            self.logger.write("Buffer number out of range!\n")
            return Invoker.FAIL
        del self.buffers[nobuffer]
        return Invoker.OK

    def cmdFocus(self, params, rawline):
        if not self.checkNotMode("focus", [Invoker.SSH]):
            return Invoker.FAIL
        hostid = None
        if len(params) > 0:
            if params[0] not in self.sshagents.keys():
                self.logger.write("Host %s not defined!\n" % params[0])
                return Invoker.FAIL
            hostid = params[0]
        if self.rt["mode"] == Invoker.HOST and not hostid and not self.focus:
            hostid = self.rt["hostid"]
        elif not hostid and not self.focus:
            self.logger.write("Incomplete command(focus): {host}\n")
            return Invoker.FAIL
        if self.focus:
            self.sshagents[self.focus].focus = False
            self.logger.write("Set focus '%s' off\n" % self.focus)
            self.focus = ""
        if hostid:
            self.sshagents[hostid].focus = True
            self.focus = hostid
            self.logger.write("Set focus '%s' on\n" % self.focus)
        return Invoker.OK

    def cmdCapture(self, params, rawline):
        hostid = None
        if len(params) > 0:
            if params[0] not in self.sshagents.keys():
                self.logger.write("Host %s not defined!\n" % params[0])
                return Invoker.FAIL
            hostid = params[0]
        if self.rt["mode"] in [Invoker.HOST, Invoker.SSH] and \
            not hostid and not self.capture:
            hostid = self.rt["hostid"] 
        elif not hostid and not self.capture:
            self.logger.write("Incomplete command(capture): {host}\n")
            return Invoker.FAIL
        if self.capture:
            self.sshagents[self.capture].capture = False
            buffer = self.sshagents[self.capture].captureBuffer
            self.buffers.append(buffer.replace("\r\n", "\n").replace("\r", "\n"))
            self.logger.write("Set capture '%s' off\n" % self.capture)
            self.capture = ""
        if hostid:
            self.sshagents[hostid].capture = True
            self.capture = hostid
            self.logger.write("Set capture '%s' on\n" % self.capture)
        return Invoker.OK

    def cmdHistory(self, params, rawline):
        if not self.rt["has_readline"]:
            return Invoker.OK
        if len(params) > 0:
            try:
                nolines = int(params[0])
            except ValueError:
                self.logger.write("Invalid number of lines %s\n" % params[0])
                return Invoker.FAIL
        else:
            nolines = 40
        hislen = readline.get_current_history_length()
        nolines = hislen - nolines
        if nolines < 0:
            nolines = 0
        
        while nolines < hislen:
            self.logger.write("%d %s\n" % (nolines, 
                readline.get_history_item(nolines)))
            nolines = nolines + 1
        return Invoker.OK

    def cmdConnect(self, params, rawline):
        hosts = []
        if len(params) > 0:
            for hostid in params:
                if hostid not in self.sshagents.keys():
                    self.logger.log(Log.LGLEVEL_NOTICE, "Host '%s' not defined!\n" % hostid)
                else:
                    hosts.append(hostid)
        elif self.rt["mode"] in [Invoker.SSH, Invoker.HOST]:
            hosts.append(self.rt["hostid"])
        elif self.rt["mode"] == Invoker.CLUSTER:
            hosts = self.config["Clusters"][self.rt["clusterid"]]
        if not hosts:
            self.logger.write("No host to be opened!\n")
            return Invoker.FAIL
        for hostid in hosts:
            if not self.sshagents[hostid].alive():
                self.sshagents[hostid].open(True)
        return Invoker.OK
    
    def cmdDisconnect(self, params, rawline):
        hosts = []
        if len(params) > 0:
            for hostid in params:
                if hostid not in self.sshagents.keys():
                    self.logger.log(Log.LGLEVEL_NOTICE, "Host '%s' not defined!\n" % hostid)
                else:
                    hosts.append(hostid)
        elif self.rt["mode"] in [Invoker.SSH, Invoker.HOST]:
            hosts.append(self.rt["hostid"])
        elif self.rt["mode"] == Invoker.CLUSTER:
            hosts = self.config["Clusters"][self.rt["clusterid"]]
        if not hosts:
            self.logger.write("No host to be closed!\n")
            return Invoker.FAIL
        for hostid in hosts:
            self.sshagents[hostid].close()
        return Invoker.OK
    
    def cmdKeof(self, params, rawline):
        hosts = []
        if len(params) > 0:
            for hostid in params:
                if hostid not in self.sshagents.keys():
                    self.logger.log(Log.LGLEVEL_NOTICE, "Host %s not defined!\n" % hostid)
                else:
                    hosts.append(hostid)
        elif self.rt["mode"] in [Invoker.SSH, Invoker.HOST]:
            hosts.append(self.rt["hostid"])
        elif self.rt["mode"] == Invoker.CLUSTER:
            hosts = self.config["Clusters"][self.rt["clusterid"]]
        if hosts:
            for hostid in hosts:
                self.setTermEofHost(hostid)
        else:
            self.logger.write("No available host to send EOF!\n")
            return Invoker.FAIL
        return Invoker.OK

    def setTermEofHost(self, hostid):
        if self.sshagents[hostid].alive():
            if hasattr(termios, 'VEOF'):
                char = termios.tcgetattr(self.sshagents[hostid].fileno())[6][termios.VEOF]
            else:
                char = chr(4)
            return self.sshChatHost(hostid, char)
        else:
            self.logger.log(Log.LGLEVEL_NOTICE, "Host %s not alive!\n" % hostid)
        return Invoker.OK
    
    def setTermEofCluster(self, clusterid):
        for hostid in self.config["Clusters"][clusterid]:
            self.setTermEofHost(hostid)
        return Invoker.OK 
    
    def cmdKcontrol(self, params, rawline):
        if not self.checkNotMode("control", [Invoker.GLOBAL]):
            return Invoker.FAIL
        if len(params) < 1:
            self.logger.write("Incomplete command(control): {char}\n")
            return Invoker.FAIL
        if len(params[0]) != 1:
            self.logger.write("Only one letter allowed\n")
            return Invoker.FAIL
        char = params[0].lower()
        code = ord(char)
        if code >= 97 and code <= 122:
            code = code - 97 + 1
        else:
            specials = {'@':0, '`':0,
            '[':27, '{':27,
            '\\':28, '|':28,
            ']':29, '}': 29,
            '^':30, '~':30,
            '_':31,
            '?':127}
            if not char in specials:
                self.logger.write("Invalid controlling charactor %s\n" % params[0])
                return Invoker.FAIL
            code = special[char]
        return self.sshChat(chr(code))

    def cmdReply(self, params, rawline):
        if not self.checkNotMode("reply", [Invoker.GLOBAL]):
            return Invoker.FAIL
        if len(params) < 1:
            self.logger.write("Incomplete command(reply): password\n")
            return Invoker.FAIL
        func = self.lookupCommand("cmdReply", params[0], Invoker.__dict__)
        if not func:
            return Invoker.FAIL
        return func(self, params[1:], rawline)

    def cmdReplyPassword(self, params, rawline):
        if self.rt["mode"] == Invoker.CLUSTER:
            for hostid in self.config["Clusters"][self.rt["clusterid"]]:
                self.sshChatHost(hostid, self.config["Hosts"][hostid]["password"]+"\n")
            return Invoker.OK
        else:
            return self.sshChatHost(self.rt["hostid"], 
                self.config["Hosts"][self.rt["hostid"]]["password"]+"\n")

    def cmdShow(self, params, rawline):
        if len(params) < 1:
            self.logger.write("Incomplete command(show): running|config\n")
            return Invoker.FAIL        
        func = self.lookupCommand("cmdShow", params[0], Invoker.__dict__)
        if not func:
            return Invoker.FAIL
        return func(self, params[1:], rawline)

    def cmdShowConfig(self, params, rawline):
        self.config.dumpConfigs(self.logger)
        return Invoker.OK
    
    def cmdShowRunning(self, params, rawline):
        info = ""
        for clusterid in self.config["Clusters"].keys():
            info = info + "[%s]:\n" % clusterid
            for hostid in self.config["Clusters"][clusterid]:
                host = self.config["Hosts"][hostid]
                info = info + "    [%s]%s@%s:%s %s\n" % (hostid, \
                    host["user"], host["hostname"], host["port"], \
                    ("on" if self.sshagents[hostid].alive() else "off"))
        info = info + "focus: " + (self.focus if self.focus else "none") + "\n"
        info = info + "mode: " + Invoker.CS[self.rt["mode"]]
        if self.rt["mode"] == Invoker.CLUSTER:
            info = info + "=" + self.rt["clusterid"]
        elif self.rt["mode"] in [Invoker.SSH, Invoker.HOST]:
            info = info + "=" + self.rt["hostid"]
        
        self.logger.write(info + "\n")
        return Invoker.OK
    
    def cmdShowBuffer(self, params, rawline):
        if len(params) > 0:
            try:
                nobuffer = int(params[0])
            except ValueError, e:
                self.logger.write("Invalid buffer number %s\n" % params[0])
                return Invoker.FAIL
            if len(self.buffers) < nobuffer+1:
                self.logger.write("Buffer number out of range\n")
                return Invoker.FAIL
            self.logger.write("* buffer number: %d\n" % nobuffer)
            self.logger.write("%s\n" % self.buffers[nobuffer])
        else:
            for i, buffer in enumerate(self.buffers):
                line = buffer[0:256].replace("\n", " ").replace("\t", " ")
                self.logger.write("* buffer number: %d\n" % i)
                self.logger.write("%s\n" % line)
        return Invoker.OK
    
    def cmdQuit(self, params = None, rawline = None):
        self.logger.write("\nGood bye!\n")
        self.reactor.stop()
        return Invoker.QUIT

    def cmdGlobal(self, params, rawline):
        if self.rt["mode"] == Invoker.SSH:
            self.sshagents[self.rt["hostid"]].focus = False
            self.prompt(1)
        self.rt["mode"] = Invoker.GLOBAL
        return Invoker.OK

    def cmdCluster(self, params, rawline):
        if len(params) < 1:
            self.logger.write("Incomplete command(cluster): {cluster}\n")
            return Invoker.FAIL
        cand = [entry for entry in self.config["Clusters"] \
                if entry.find(params[0]) == 0]
        if len(cand) == 0:
            self.logger.write("Cluster %s not defined\n" % params[0])
            return Invoker.FAIL
        elif len(cand) > 1:
            self.logger.write("Ambiguous cluster '%s'%s\n"%(params[0], str(cand)))
            return Invoker.FAIL
        
        if self.rt["mode"] == Invoker.SSH:
            self.sshagents[self.rt["hostid"]].focus = False
            self.prompt(1)
        self.rt["mode"] = Invoker.CLUSTER
        self.rt["clusterid"] = cand[0]
        for hostid in self.config["Clusters"][self.rt["clusterid"]]:
            if not self.sshagents[hostid].alive():
                self.sshagents[hostid].open()
        return Invoker.OK

    def cmdHost(self, params, rawline):
        if len(params) < 1:
            self.logger.write("Incomplete command(host): {host}\n")
            return Invoker.FAIL
        cand = [entry for entry in self.config["Hosts"] \
                if entry.find(params[0]) == 0]
        if len(cand) == 0:
            self.logger.write("Host %s not defined\n" % params[0])
            return Invoker.FAIL
        elif len(cand) > 1:
            self.logger.write("Ambiguous host '%s'%s\n"%(params[0], str(cand)))
            return Invoker.FAIL
        
        if self.rt["mode"] == Invoker.SSH:
            self.sshagents[self.rt["hostid"]].focus = False
            self.prompt(1)
        self.rt["mode"] = Invoker.HOST
        self.rt["hostid"] = cand[0]
        if not self.sshagents[self.rt["hostid"]].alive():
            self.sshagents[self.rt["hostid"]].open()
        return Invoker.OK
    
    def cmdSsh(self, params, rawline):
        if len(params) < 1:
            if self.rt["mode"] != Invoker.HOST:
                self.logger.write("Incomplete command(ssh): {host}\n")
                return Invoker.FAIL
            params.append(self.rt["hostid"])

        cand = [entry for entry in self.config["Hosts"] \
                if entry.find(params[0]) == 0]
        if len(cand) == 0:
            self.logger.write("Host %s not defined\n" % params[0])
            return Invoker.FAIL
        elif len(cand) > 1:
            self.logger.write("Ambiguous host '%s'%s\n"%(params[0], str(cand)))
            return Invoker.FAIL
        self.rt["mode"] = Invoker.SSH
        self.rt["hostid"] = cand[0]
        
        if self.focus:
            self.sshagents[self.focus].focus = False
            self.focus = ""
        if not self.sshagents[self.rt["hostid"]].alive():
            self.sshagents[self.rt["hostid"]].open()
        self.sshagents[self.rt["hostid"]].focus = True
        self.sshagents[self.rt["hostid"]].last(2)
        
        return Invoker.OK
    
    def sshChat(self, message):
        if self.rt["mode"] in [Invoker.SSH, Invoker.HOST]:
            return self.sshChatHost(self.rt["hostid"], message)
        elif self.rt["mode"] == Invoker.CLUSTER:
            return self.sshChatCluster(self.rt["clusterid"], message)
        else:
            self.logger.write("Can't chat with ssh in current mode!\n")
            return Invoker.FAIL

    def sshChatHost(self, hostid, message):
        if self.sshagents[hostid].alive():
            self.sshagents[hostid].write(message)
        else:
            self.logger.log(Log.LGLEVEL_NOTICE, "Host %s not alive\n" % hostid)
        return Invoker.OK
    
    def sshChatCluster(self, clusterid, message):
        for hostid in self.config["Clusters"][clusterid]:
            self.sshChatHost(hostid, message)
        return Invoker.OK
    
    def cmdRun(self, params, rawline):
        if not self.checkNotMode("run", [Invoker.GLOBAL]):
            return Invoker.FAIL
        #if len(params) < 1:
            #self.logger.write("Incomplete command(run): {string}\n")
            #return Invoker.FAIL
        return self.sshChat(" ".join(params)+"\n")

    def cmdSend(self, params, rawline):
        if not self.checkNotMode("run", [Invoker.GLOBAL]):
            return Invoker.FAIL
        if len(params) < 1:
            self.logger.write("Incomplete command(send): {string}\n")
            return Invoker.FAIL
        return self.sshChat(" ".join(params))

    def cmdHrun(self, params, rawline):
        if len(params) < 1:
            self.logger.write("Incomplete command(hrun): {host} {string}\n")
            return Invoker.FAIL
        hostid = params[0]
        if hostid not in self.sshagents.keys():
            self.logger.write("Host %s not defined!\n" % hostid)
            return Invoker.FAIL
        del params[0]
        return self.sshChatHost(hostid, " ".join(params)+"\n")

    def cmdHsend(self, params, rawline):
        if len(params) < 1:
            self.logger.write("Incomplete command(hsend): {host} {string}\n")
            return Invoker.FAIL
        hostid = params[0]
        if hostid not in self.sshagents.keys():
            self.logger.write("Host %s not defined!\n" % hostid)
            return Invoker.FAIL
        del params[0]
        if len(params) < 1:
            self.logger.write("Incomplete command(hsend): {string}\n")
            return Invoker.FAIL
        return self.sshChatHost(hostid, " ".join(params))

    def cmdLast(self, params, rawline):
        if not self.checkNotMode("last", [Invoker.GLOBAL]):
            return Invoker.FAIL
        if len(params) > 0:
            try:
                nolines = int(params[0])
            except ValueError:
                self.logger.write("Invalid number of lines %s\n" % params[0])
                return Invoker.FAIL
        else:
            if self.rt["mode"] == Invoker.CLUSTER:
                nolines = 20
            else:
                nolines = 30
        if self.rt["mode"] == Invoker.CLUSTER:
            first = True
            for hostid in self.config["Clusters"][self.rt["clusterid"]]:
                if not first:
                    self.logger.write("\n")
                first = False
                self.logger.write("%s\n[%s]\n" % ('*' * 40, hostid))
                self.sshagents[hostid].last(nolines)
        elif self.rt["mode"] in [Invoker.HOST, Invoker.SSH]:
            self.sshagents[self.rt["hostid"]].last(nolines)
        self.logger.write("\n")
        return Invoker.OK
    
    def prompt(self, clear = 0, ret = False):
        if self.rt["mode"] == Invoker.SSH:
            return ""
        if clear:
            self.logger.write('\n' * clear)
        
        if self.rt["mode"] == Invoker.GLOBAL:
            pstr = "> "
        elif self.rt["mode"] == Invoker.CLUSTER:
            pstr = "%s> " % self.rt["clusterid"]
        else:
            pstr = "%s> " % self.rt["hostid"]
        
        if not ret:
            self.logger.write(pstr)
        return pstr

    def splitCommand(self, cmd):
        tokens = []
        tcmd = cmd.lower()
        pos = 0
        prev = 0
        while pos < len(tcmd):
            if tcmd[pos] != cmd[pos]:
                if prev < pos:
                    tokens.append(tcmd[prev:pos])
                prev = pos
            pos = pos + 1
        if prev < len(tcmd):
            tokens.append(tcmd[prev:])
        return tokens
        
    def isCommandPart(self, cmd, entry):
        if entry.find(cmd) == 0:
            remain = entry[len(cmd):]
            for c in remain:
                if ord(c) >= ord('A') and ord(c) <= ord('Z'):
                    return False
            return True
        return False
    
    def lookupCommand(self, prefix, token, pool):
        cmd = prefix + token.lower().capitalize()
        cand = [entry for entry in pool.keys() if self.isCommandPart(cmd, entry)]
        if len(cand) == 1:
            return pool[cand[0]]
        if len(cand) > 1:
            cand = [entry[len(prefix):].lower() for entry in cand]
            self.logger.write("Ambiguous token '%s'%s!\n" % (token, str(cand)))
            return False
        else: 
            #self.logger.write("Unrecognized token '%s'!\n" % token)
            return None

    def checkNotMode(self, cmd, modes):
        if self.rt["mode"] in modes:
            self.logger.write("Command '%s' not supported in mode '%s'!\n" % (cmd, 
                Invoker.CS[self.rt["mode"]]))
            return False
        return True

    def getAvailableTokens(self, buffer):
        cand = []
        tokens = Utilities.cmd_parse(buffer.strip())
        if self.rt["mode"] == Invoker.SSH:
            if not tokens or tokens[0] != self.config["Global"]["sshbatchid"]:
                return []
            tokens = tokens[1:]
            buffer = " ".join(tokens) + (" " if buffer[-1] == " " else "")
        if not tokens:
            for ttokens in self.tokens:
                cand.append(ttokens[0])
        else:    
            for ttokens in self.tokens:
                if " ".join(ttokens).find(buffer) == 0:
                    idx = len(tokens)
                    if tokens[-1] == ttokens[idx-1]:
                        if len(ttokens) > idx and buffer[-1] == " ": 
                            cand.append(ttokens[idx])
                    else:
                        cand.append(ttokens[idx-1])
        return cand
